import Cesium from '../Ces/Cesium'
import WeTaskProcessorPool from '../Core/WeTaskProcessorPool'
import WeDataProvider from './WeDataProvider'
import WeDisplay from './WeDisplay'
import WeDisplayParameter from './WeDisplayParameter'
import { fsSource, vsSource } from './WeIsoSurfaceShaders'
import WeTextureUniform from './WeTextureUniform'
import WeUniform from './WeUniform'

class WeIsoSurface {
  private weUniform: WeUniform
  private display: WeDisplay
  private dataProvider: WeDataProvider

  private drawCommands: any = []//: Cesium.DrawCommand[] = []
  private weTextureUniforms: WeTextureUniform[] = []

  private initialized: boolean = false

  private isoValues: number[]
  private doubleSide: boolean = false
  private verbose: boolean = false
  private getCacheUrlCallback: (arg1: string, arg2: string) => string
  private taskProcessorPool: WeTaskProcessorPool

  constructor(options) {
    this.taskProcessorPool = options.taskProcessorPool
    this.isoValues = options.isoValues
    this.display = options.display
    this.dataProvider = options.dataProvider
    this.doubleSide = options.doubleSide ?? false
    this.verbose = options.verbose ?? false
    this.getCacheUrlCallback = options.getCacheUrlCallback

    const meterHeight = this.dataProvider.externalData?.size?.depth
    const floatUniform = {
      bufferSize: options.bufferSize || 2046,
      isoNumber: this.isoValues.length,
      meterHeight,
      offsetZ: options.offsetZ || 0,
      scaleZ: options.scaleZ || 1,
      multiple: options.multiple || 1,
      normalFactor: options.normalFactor ?? 1.0,
      isLinear: this.display.valueAndColorRamp?.linear ? 1.0 : 0.0,
    }
    const vec3Uniform = {
      isoValues: new Cesium.Cartesian3(this.isoValues[0], this.isoValues[1], this.isoValues[2]),
    }
    const vec4Uniform = {
      customExtent: options.customExtent || this.dataProvider._imageryLayer.rectangle
    }

    this.weUniform = new WeUniform({
      floatUniform,
      vec3Uniform,
      vec4Uniform,
      name: 'WeIsoSurface',
    })
    const sampler = new Cesium.Sampler({
      wrapS: Cesium.TextureWrap.CLAMP_TO_EDGE,
      wrapT: Cesium.TextureWrap.CLAMP_TO_EDGE,
      wrapR: Cesium.TextureWrap.CLAMP_TO_EDGE,
      minificationFilter: Cesium.TextureMinificationFilter.NEAREST,
      magnificationFilter: Cesium.TextureMagnificationFilter.NEAREST,
    })
    const animation = false
    this.weTextureUniforms = [
      new WeTextureUniform({ sampler, animation, calculateNormal: false, display: this.display }),
      new WeTextureUniform({ sampler, animation, calculateNormal: false, display: this.display }),
      new WeTextureUniform({ sampler, animation, calculateNormal: false, display: this.display }),
    ]
  }

  async updateData(context, image1, image2) {
    if (this.drawCommands.length === 0) return
    this.loadData(context, image1 ?? image2)
  }

  async loadData(context, image) {
    const { multiple } = this.weUniform.Properties
    const width = this.weUniform.Properties.bufferSize
    const height = width
    const url = image.url
    const { getCacheUrlCallback } = this

    this.display.imageryLayer.loadingRef++
    if (getCacheUrlCallback) {
      const promises = []
      for (let i = 0; i < this.isoValues.length; i++) {
        const url1 = getCacheUrlCallback(url, this.isoValues[i].toFixed(1))
        promises.push(Cesium.Resource.fetchArrayBuffer(url1))
      }
      Promise.all(promises)
        .then((res) => {
          const images = []
          res.forEach((arrayBuffer) => {
            images.push({
              width,
              height,
              depth: multiple,
              arrayBufferView: new Float32Array(arrayBuffer),
              url,
            })
          })
          this.updateImage(context, images)
        })
        .finally(() => {
          this.display.imageryLayer.loadingRef--
        })
    } else {
      const taskProceesor = await this.taskProcessorPool.getTaskProcessor()
      const promises = []
      for (let i = 0; i < this.isoValues.length; i++) {
        const task = taskProceesor.scheduleTask({
          rectangle: {
            west: image.west,
            south: image.south,
            east: image.east,
            north: image.north,
          },
          verbose: this.verbose,
          multiple,
          scale: this.display.valueDecoder.scale,
          offset: this.display.valueDecoder.offset,
          bufferSize: this.weUniform.Properties.bufferSize,
          isoValues: [this.isoValues[i]],
          source: image.source
        })
        promises.push(task)
      }
      Promise.all(promises).then((res) => {
        const images = []
        res.forEach((arrayBufferView) => {
          images.push({
            width,
            height,
            depth: multiple,
            arrayBufferView,
            url,
          })
        })
        this.updateImage(context, images)
      }).finally(() => {
        this.display.imageryLayer.loadingRef--
      })
    }
  }

  updateImage(context, images) {
    if (this.isDestroyed() || images.length === 0) return

    if (this.drawCommands.length === 0) {
      this.rebuild(context)
    }
    for (let i = 0; i < images.length; i++) {
      this.weTextureUniforms[i].updateData(context, images[i], null, {
        context,
        pixelFormat: Cesium.PixelFormat.RGB,
        pixelDatatype: Cesium.PixelDatatype.FLOAT,
        flipY: false,
      })
    }
  }

  rebuild(context) {
    this.drawCommands.forEach((e) => {
      e.shaderProgram?.destroy()
    })
    this.drawCommands.length = 0

    const { multiple } = this.weUniform.Properties
    const renderState = Cesium.RenderState.fromCache({
      cull: {
        enabled: !this.doubleSide,
        face: Cesium.CullFace.FRONT,
      },
      depthTest: {
        enabled: true,
      },
      blending: Cesium.BlendingState.ALPHA_BLEND,
    })

    const width = this.weUniform.Properties.bufferSize
    const height = width
    const numIsoSurface = this.isoValues.length

    const buildVertexArray = (num: number) => {
      const numVertexs = width * height * num
      const position = new Float32Array(numVertexs)
      const geometry = new Cesium.Geometry({
        attributes: {
          position: new Cesium.GeometryAttribute({
            componentDatatype: Cesium.ComponentDatatype.FLOAT,
            componentsPerAttribute: 1,
            values: position,
          }),
        },
      })
      const attributeLocations = Cesium.GeometryPipeline.createAttributeLocations(geometry)
      const vertexArray = Cesium.VertexArray.fromGeometry({
        context,
        geometry,
        attributeLocations,
        bufferUsage: Cesium.BufferUsage.STATIC_DRAW,
      })
      return { vertexArray, attributeLocations }
    }

    const { vertexArray, attributeLocations } = buildVertexArray(multiple)

    const vertexShaderSource = `
    ${multiple > 1 ? '#define IS_MULTIPLE_ISO' : ''}
    ${this.display.getFragmentShaderSource(true, true)}
    ${this.weUniform.getSource(true)}
    ${vsSource}
    `

    const shaderProgram = Cesium.ShaderProgram.fromCache({
      context,
      vertexShaderSource,
      fragmentShaderSource: fsSource,
      attributeLocations,
    })

    for (let i = 0; i < numIsoSurface; i++) {
      const uniformMap = {
        basePositionTexture: () => this.weTextureUniforms[i].baseTexture,
        isoIndexOffset: () => i,
      }
      this.display.setupUniformMap(uniformMap)
      this.weUniform.setupUniformMap(uniformMap)

      this.drawCommands.push(
        new Cesium.DrawCommand({
          vertexArray,
          shaderProgram,
          uniformMap,
          renderState,
          pass: Cesium.Pass.TRANSLUCENT,
        }),
      )
    }
  }

  update(frameState) {
    if (!frameState.passes.render) return

    if (!this.initialized) {
      this.initialized = true
      this.loadData(frameState.context, this.dataProvider.dataRequest.requestCaches[0])
    }

    if (this.weUniform.Properties.scaleZ !== WeDisplayParameter.scaleZ) {
      this.weUniform.Properties.scaleZ = WeDisplayParameter.scaleZ
    }

    const drawCommands = this.drawCommands
    for (let i = 0; i < drawCommands.length; i++) {
      const textureUniform = this.weTextureUniforms[i]
      textureUniform.update(frameState)
      if (!textureUniform.ready) return
      const drawCommand = drawCommands[i]
      drawCommand.pass = i < drawCommands.length - 1 ? Cesium.Pass.TRANSLUCENT : Cesium.Pass.OPAQUE
      frameState.commandList.push(drawCommand)
    }
  }

  destroy() {
    this.drawCommands.forEach((e) => {
      e.shaderProgram?.destroy()
    })
    this.drawCommands.length = 0


    this.weTextureUniforms.forEach((e) => {
      e.destroy()
    })
    this.weTextureUniforms.length = 0

    return Cesium.destroyObject(this)
  }

  isDestroyed() {
    return false
  }
}

export default WeIsoSurface
